package com.qd.panda.service.shop.order;

import cn.hutool.core.math.Money;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cdqidi.constant.ConstantDto;
import com.cdqidi.db.BaseService;
import com.cdqidi.dto.ApiResult;
import com.cdqidi.dto.CacheKey;
import com.cdqidi.excel.ExcelExport;
import com.cdqidi.excel.util.ExportPage;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.DateUtil;
import com.cdqidi.util.RedisTemplateUtil;
import com.cdqidi.util.SpringContextHolder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.qd.common.panda.config.PandaParkConfigDTO;
import com.qd.common.panda.constant.PandaConstant;
import com.qd.common.panda.domain.entity.carpark.calculate.CalculatePriceDTO;
import com.qd.common.panda.domain.entity.carpark.calculate.CalculatePriceDetailDTO;
import com.qd.common.panda.domain.entity.order.OrderStatisticsDTO;
import com.qd.common.panda.domain.entity.shop.order.ProductOrderDTO;
import com.qd.common.panda.domain.entity.shop.order.ProductOrderPreferentialLogDTO;
import com.qd.common.panda.domain.entity.shop.order.ShopOrderDTO;
import com.qd.common.panda.domain.entity.stroke.PayParamDTO;
import com.qd.common.panda.domain.enums.PreferentialCategoryEnum;
import com.qd.common.sys.util.SecurityUtils;
import com.qd.panda.mapper.shop.order.ProductOrderMapper;
import com.qd.panda.model.Product;
import com.qd.panda.model.ProductOrder;
import com.qd.panda.model.ShopAddress;
import com.qd.panda.service.card.UserCardHolderService;
import com.qd.panda.service.shop.address.ShopAddressService;
import com.qd.panda.service.shop.product.ProductService;
import com.qd.panda.service.stroke.BigDecimalUtil;
import com.qd.panda.service.stroke.DisplayUtil;
import com.qd.panda.service.stroke.UserStrokeHolderService;
import com.qd.panda.service.stroke.order.GasOrderService;
import com.qd.pay.config.PayConstant;
import com.qd.pay.domain.OrderDTO;
import com.qd.pay.service.order.OrderHolderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.qd.panda.service.stroke.UserStrokeHolderService.REDIS_LOCKED_ERR;
import static java.util.stream.Collectors.toList;

/**
 * 订单
 *
 * @author sjk
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class ProductOrderService extends BaseService<ProductOrderMapper, ProductOrder> {

    private static final String PREFIX_ID = "panda_product_order_orderId_{0}";
    private static final String PREFIX_ORDER_ID = "product_order_orderId_{0}";

    private final RedisTemplateUtil<ProductOrder> redisTemplateUtil;
    private final RedisTemplateUtil<PayParamDTO> payParamRedisTemplateUtil;

    private final ProductService productService;
    private final ShopAddressService shopAddressService;
    private final PandaParkConfigDTO pandaParkConfig;
    private final OrderHolderService orderHolderService;
    private final UserCardHolderService userCardHolderService;
    private final GasOrderService gasOrderService;
    private final ProductOrderPreferentialLogService productOrderPreferentialLogService;

    private final RedissonClient redissonClient;
    @Resource
    private HttpServletResponse response;
    @Resource
    private ObjectMapper objectMapper;

    /**
     * 保存
     *
     * @param dto 页面对象
     * @return 页面对象
     */

    @Transactional(rollbackFor = Exception.class)
    public ProductOrderDTO save(ProductOrderDTO dto) {
        final ProductOrder model = dtoToModel(dto);
        try {
            if (super.save(model)) {
                return modelToDto(model);
            }
            throw new ApiException("添加失败");
        } finally {
            clearCache(model);
            model.freeData();
        }
    }

    /**
     * 修改
     *
     * @param dto 页面对象
     * @return 页面对象
     */

    @Transactional(rollbackFor = Exception.class)
    public ProductOrderDTO update(ProductOrderDTO dto) {
        final ProductOrder historyModel = Optional.ofNullable(getById(dto.getOrderId())).orElseThrow(() -> new ApiException("ID不存在,不能更新"));
        final ProductOrder model = dtoToModel(dto);
        try {
            if (super.updateById(model)) {
                return modelToDto(model);
            }
            throw new ApiException("更新失败");
        } finally {
            clearCache(historyModel);
            historyModel.freeData();
            model.freeData();
        }
    }

    /**
     * 批量添加
     *
     * @param mList 数据库对象
     */

    @Transactional(rollbackFor = Exception.class)
    public void saveBatch(List<ProductOrder> mList) {
        try {
            if (!super.saveBatch(mList)) {
                throw new ApiException("批量添加失败");
            }
        } finally {
            clearCaches(mList);
        }
    }

    /**
     * 批量更新
     *
     * @param mList 数据库对象
     */

    @Transactional(rollbackFor = Exception.class)
    public void updateBatchIds(List<ProductOrder> mList) {
        final List<String> ids = mList.stream().map(ProductOrder::getOrderId).collect(toList());
        final List<ProductOrder> historyModelList = checkIds(ids);
        try {
            if (!super.updateBatchById(mList)) {
                throw new ApiException("批量更新失败");
            }
        } finally {
            clearCaches(historyModelList);
            historyModelList.clear();
            ids.clear();
        }
    }

    /**
     * 删除，单个
     *
     * @param orderId 主键
     */

    @Transactional(rollbackFor = Exception.class)
    public void removeById(String orderId) {
        final ProductOrder historyModel = Optional.ofNullable(getById(orderId)).orElseThrow(() -> new ApiException("数据不存在,不能删除"));
        try {
            if (!super.removeById(orderId)) {
                throw new ApiException("删除失败");
            }
        } finally {
            clearCache(historyModel);
            historyModel.freeData();
        }
    }

    /**
     * 批量删除
     *
     * @param ids 主键集合
     */

    @Transactional(rollbackFor = Exception.class)
    public void removeBatchIds(List<String> ids) {
        final List<ProductOrder> historyModelList = checkIds(ids);
        try {
            if (!super.removeByIds(ids)) {
                throw new ApiException("批量删除失败");
            }
        } finally {
            clearCaches(historyModelList);
            historyModelList.clear();
        }
    }

    /**
     * 根据传入对象构建查询参数，判断是否存在
     *
     * @param dto 参数
     * @return 存在为true
     */

    @Transactional(readOnly = true)
    public Boolean isExists(ProductOrderDTO dto) {
        final QueryWrapper<ProductOrder> queryWrapper = createQueryWrapper(dto);
        final boolean exists = super.count(queryWrapper) > 0;
        queryWrapper.clear();
        return exists;
    }

    /**
     * 根据主键判断数据是否存在
     *
     * @param orderId 主键
     * @return 存在为true
     */

    @Transactional(readOnly = true)
    public Boolean isExistsById(String orderId) {
        boolean exists = false;
        if (StringUtils.hasLength(orderId)) {
            final ProductOrder model = getById(orderId);
            exists = (null != model);
            if (exists) {
                model.freeData();
            }
        }
        return exists;
    }

    /**
     * 根据主键获取数据
     *
     * @param orderId 主键
     * @return 单条记录
     */

    @Transactional(readOnly = true)
    public ProductOrder getById(String orderId) {
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, orderId);
        return redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY), () -> super.getById(orderId), ProductOrder.class);
    }

    /**
     * 根据主键获取数据
     *
     * @param orderId 主键
     * @return 单条记录，转换成页面对象
     */

    @Transactional(readOnly = true)
    public ProductOrderDTO getByIdDto(String orderId) {
        return Optional.ofNullable(getById(orderId)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录
     */

    @Transactional(readOnly = true)
    public ProductOrder getOne(ProductOrderDTO dto) {
        final QueryWrapper<ProductOrder> queryWrapper = createQueryWrapper(dto);
        ProductOrder model = super.getOne(queryWrapper);
        queryWrapper.clear();
        return model;
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录，转换成页面对象
     */

    @Transactional(readOnly = true)
    public ProductOrderDTO getOneDto(ProductOrderDTO dto) {
        return Optional.ofNullable(getOne(dto)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据
     */

    @Transactional(readOnly = true)
    public List<ProductOrder> getList(ProductOrderDTO dto) {
        final QueryWrapper<ProductOrder> queryWrapper = createQueryWrapper(dto);
        final List<ProductOrder> list = super.list(queryWrapper);
        queryWrapper.clear();
        return list;
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据，转换成页面对象
     */

    @Transactional(readOnly = true)
    public List<ProductOrderDTO> getListDto(ProductOrderDTO dto) {
        return modelToDtoList(getList(dto));
    }

    /**
     * 分页数据
     *
     * @param page 分页对象
     * @param dto  查询参数
     * @return 分页信息
     */

    @Transactional(readOnly = true)
    public IPage<ProductOrderDTO> page(Page<ProductOrder> page, ProductOrderDTO dto) {
        final QueryWrapper<ProductOrder> queryWrapper = createQueryWrapper(dto, "t1.");
        final IPage<ProductOrderDTO> iPage = baseMapper.page(page, queryWrapper);
        Optional.ofNullable(iPage.getRecords()).ifPresent(l -> l.forEach(this::buildDisplay));
        queryWrapper.clear();
        return iPage;
    }

    /**
     * 导出Excel
     *
     * @param query 查询条件
     */

    @Transactional(readOnly = true)
    public void export(ProductOrderDTO query) {
        final ExportPage<ProductOrder, ProductOrderDTO> exportPage = new ExportPage<>();
        try {
            final List<ProductOrderDTO> list = exportPage.getAllForPage(page -> page(page, query).getRecords());
            try {
                ExcelExport.downloadExcel(response, ProductOrderDTO.class, () -> list);
            } catch (IOException e) {
                log.error("导出失败", e);
                throw new ApiException("导出失败");
            }
        } finally {
            exportPage.freeData();
        }
    }

    /**
     * 清空缓存
     *
     * @param model 数据库对象
     */

    public void clearCache(ProductOrder model) {
        if (null == model) {
            return;
        }
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, model.getOrderId()));
    }

    /**
     * 批量清空缓存
     *
     * @param list 数据库对象
     */

    public void clearCaches(List<ProductOrder> list) {
        if (null == list || list.isEmpty()) {
            return;
        }
        List<String> ids = list.stream().map(model -> RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, model.getOrderId())).collect(toList());
        if (!ids.isEmpty()) {
            redisTemplateUtil.deleteByKey(ids);
            ids.clear();
        }
    }

    /**
     * 数据库对象转页面对象
     *
     * @param model 数据库对象
     * @return 页面对象
     */

    public ProductOrderDTO modelToDto(ProductOrder model) {
        if (null == model) {
            return null;
        }
        final ProductOrderDTO dto = new ProductOrderDTO();
        dto.setOrderId(model.getOrderId());
        dto.setOrderAmount(model.getOrderAmount());
        dto.setPayAmount(model.getPayAmount());
        dto.setDiscountAmount(model.getDiscountAmount());
        dto.setPayStatus(model.getPayStatus());
        dto.setBid(model.getBid());
        dto.setAddTime(model.getAddTime());
        dto.setUserId(model.getUserId());
        dto.setProductCode(model.getProductCode());
        dto.setAddressId(model.getAddressId());
        dto.setFactory(model.getFactory());
        buildDisplay(dto);
        return dto;
    }

    /**
     * 页面对象转数据库对象
     *
     * @param dto 页面对象
     * @return 数据库对象
     */

    public ProductOrder dtoToModel(ProductOrderDTO dto) {
        if (null == dto) {
            return null;
        }
        final ProductOrder model = new ProductOrder();
        model.setOrderId(dto.getOrderId());
        model.setOrderAmount(dto.getOrderAmount());
        model.setPayAmount(dto.getPayAmount());
        model.setDiscountAmount(dto.getDiscountAmount());
        model.setPayStatus(dto.getPayStatus());
        model.setBid(dto.getBid());
        model.setAddTime(dto.getAddTime());
        model.setUserId(dto.getUserId());
        model.setProductCode(dto.getProductCode());
        model.setAddressId(dto.getAddressId());
        model.setFactory(dto.getFactory());
        return model;
    }

    /**
     * 码表转换
     *
     * @param dto 页面对象
     */
    public void buildDisplay(ProductOrderDTO dto) {
        dto.setPayStatusDisplay(DisplayUtil.getPayStatusDisplay(dto.getPayStatus()));
        if (!StringUtils.hasLength(dto.getProductCodeDisplay())) {
            dto.setProductCodeDisplay(productService.idToDisplay(dto.getProductCode()));
        }
        if (!StringUtils.hasLength(dto.getAddressIdDisplay())) {
            ShopAddress shopAddress = shopAddressService.getById(dto.getAddressId());
            if (null != shopAddress) {
                dto.setAddressIdDisplay(shopAddress.getRegion() + shopAddress.getAddress());
                dto.setName(shopAddress.getName());
                dto.setPhone(shopAddress.getPhone());
                shopAddress.freeData();
            }
        }
        dto.setOrderAmountDisplay(Optional.ofNullable(dto.getOrderAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setPayAmountDisplay(Optional.ofNullable(dto.getPayAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setDiscountAmountDisplay(Optional.ofNullable(dto.getDiscountAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
    }

    /**
     * 数据库对象转页面对象
     *
     * @param list 数据库对象
     * @return 页面对象
     */

    public List<ProductOrderDTO> modelToDtoList(List<ProductOrder> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::modelToDto).collect(toList());
    }

    /**
     * 页面对象转数据库对象
     *
     * @param list 页面对象
     * @return 数据库对象
     */

    public List<ProductOrder> dtoToModelList(List<ProductOrderDTO> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::dtoToModel).collect(toList());
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto 查询参数，不为空的属性会被自动加入
     */
    public QueryWrapper<ProductOrder> createQueryWrapper(ProductOrderDTO dto) {
        return createQueryWrapper(dto, "");
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto        查询参数，不为空的属性会被自动加入
     * @param tableAlias 表别名
     */
    public QueryWrapper<ProductOrder> createQueryWrapper(ProductOrderDTO dto, String tableAlias) {
        final LocalDate now = LocalDate.now();
        final QueryWrapper<ProductOrder> queryWrapper = new QueryWrapper<>();
        if (null != dto) {
            if (StringUtils.hasLength(tableAlias) && tableAlias.charAt(tableAlias.length() - 1) != '.') {
                tableAlias += ".";
            }
            if (StringUtils.hasLength(dto.getBeginTime()) && DateUtil.strToLocalDate(dto.getBeginTime()).isAfter(now)) {
                throw new ApiException("开始时间不能大于当前时间");
            }
            if (StringUtils.hasLength(dto.getEndTime()) && DateUtil.strToLocalDate(dto.getEndTime()).isAfter(now)) {
                throw new ApiException("结束时间不能大于当前时间");
            }
            if (StringUtils.hasLength(dto.getBeginTime())) {
                queryWrapper.apply("date_format(t1.add_time,'%Y-%m-%d') >= {0}", dto.getBeginTime());
            }
            if (StringUtils.hasLength(dto.getEndTime())) {
                queryWrapper.apply("date_format(t1.add_time,'%Y-%m-%d') <= {0}", dto.getEndTime());
            }
            if (StringUtils.hasLength(dto.getOrderId())) {
                queryWrapper.eq(tableAlias + "order_id", dto.getOrderId());
            }
            if (StringUtils.hasLength(dto.getUserId())) {
                queryWrapper.eq(tableAlias + "user_id", dto.getUserId());
            }
            if (null != dto.getPayStatus()) {
                queryWrapper.eq(tableAlias + "pay_status", dto.getPayStatus());
            }
            if (StringUtils.hasLength(dto.getName())) {
                queryWrapper.like("t3.name", dto.getName());
            }
            if (StringUtils.hasLength(dto.getPhone())) {
                queryWrapper.eq("t3.phone", dto.getPhone());
            }
            if (StringUtils.hasLength(dto.getProductCodeDisplay())) {
                queryWrapper.eq("t2.name", dto.getProductCodeDisplay());
            }
        }
        queryWrapper.orderByDesc(tableAlias + "add_time");
        queryWrapper.orderByAsc(tableAlias + "product_code");
        queryWrapper.orderByAsc(tableAlias + "user_id");
        return queryWrapper;
    }

    /**
     * 批量检查主键在数据库中是否存在
     *
     * @param ids 主键集合
     * @return 主键查询出来的集合
     */

    public List<ProductOrder> checkIds(List<String> ids) {
        if (null == ids || ids.isEmpty()) {
            throw new ApiException("ids不能为空");
        }
        final List<ProductOrder> list = super.listByIds(ids);
        if (list.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(ids.toArray()));
        }
        if (ids.size() == list.size()) {
            return list;
        }
        List<String> dbIds = list.stream().map(ProductOrder::getOrderId).collect(toList());
        final List<String> notExistsList = ids.stream().filter(id -> !dbIds.contains(id)).collect(toList());
        dbIds.clear();
        if (!notExistsList.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(notExistsList.toArray()));
        }
        return list;
    }

    @Transactional(readOnly = true)
    public OrderStatisticsDTO totalOrderStatistics(ProductOrderDTO dto) {
        QueryWrapper<ProductOrder> queryWrapper = createQueryWrapper(dto, "t1.");
        OrderStatisticsDTO orderStatisticsDTO = super.getBaseMapper().totalOrderStatistics(queryWrapper);
        if (null == orderStatisticsDTO) {
            orderStatisticsDTO = new OrderStatisticsDTO();
        }
        if (null == orderStatisticsDTO.getQuantity()) {
            orderStatisticsDTO.setQuantity(0);
        }
        if (null == orderStatisticsDTO.getOrderAmount()) {
            orderStatisticsDTO.setOrderAmount(new BigDecimal(0));
        }
        if (null == orderStatisticsDTO.getPayAmount()) {
            orderStatisticsDTO.setPayAmount(new BigDecimal(0));
        }
        if (null == orderStatisticsDTO.getDiscountAmount()) {
            orderStatisticsDTO.setDiscountAmount(new BigDecimal(0));
        }
        orderStatisticsDTO.setOrderAmountDisplay(BigDecimalUtil.buildDefaultScale(orderStatisticsDTO.getOrderAmount()).stripTrailingZeros().toPlainString());
        orderStatisticsDTO.setPayAmountDisplay(BigDecimalUtil.buildDefaultScale(orderStatisticsDTO.getPayAmount()).stripTrailingZeros().toPlainString());
        orderStatisticsDTO.setDiscountAmountDisplay(BigDecimalUtil.buildDefaultScale(orderStatisticsDTO.getDiscountAmount()).stripTrailingZeros().toPlainString());
        return orderStatisticsDTO;
    }

    @Transactional(rollbackFor = Exception.class)
    public PayParamDTO createOrder(ShopOrderDTO shopOrderDTO) {
        ShopAddress shopAddress = Optional.ofNullable(shopAddressService.getById(shopOrderDTO.getAddressId())).orElseThrow(() -> new ApiException("地址ID值错误"));
        Product product = Optional.ofNullable(productService.getById(shopOrderDTO.getProductCode())).orElseThrow(() -> new ApiException("商品编号值错误"));
        if (!shopOrderDTO.getUserId().equals(shopAddress.getUserId())) {
            throw new ApiException("没有权限购买");
        }
        //实际支付金额
        BigDecimal payAmount = product.getPrice();
        String orderId = IdWorker.getIdStr();

        CalculatePriceDTO stationCalculatePrice = new CalculatePriceDTO();
        stationCalculatePrice.setPrice(product.getPrice());
        stationCalculatePrice.setCalculatePrice(product.getPrice());
        LocalDateTime now = LocalDateTime.now();
        if (StringUtils.hasLength(shopOrderDTO.getCardId())) {
            userCardHolderService.validatorAndGetCardInfo(shopOrderDTO.getCardId(), stationCalculatePrice.getPrice(), PandaConstant.CARD_TYPE_GAS, null);
            gasOrderService.buildCardInfo(orderId, shopOrderDTO.getCardId(), stationCalculatePrice);
            savePreferentialLog(orderId, now, stationCalculatePrice);
        }
        saveOrder(shopAddress, product, orderId, stationCalculatePrice, now);

        PayParamDTO orderPay = createOrderPay(orderId, payAmount);
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ORDER_ID, orderId);
        //存储支付参数，让没有支付的订单可以在规定时间内在次支付,30分钟有效
        payParamRedisTemplateUtil.set(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_MIN * 30), orderPay);
        return orderPay;
    }

    @Transactional(rollbackFor = Exception.class)
    public void savePreferentialLog(String orderId, LocalDateTime now, CalculatePriceDTO stationCalculatePrice) {
        final List<CalculatePriceDetailDTO> detailList = stationCalculatePrice.getDetailList();
        if (null != detailList && !detailList.isEmpty()) {
            for (CalculatePriceDetailDTO calculatePriceDetailDTO : detailList) {
                ProductOrderPreferentialLogDTO productOrderPreferentialLogDTO = new ProductOrderPreferentialLogDTO();
                productOrderPreferentialLogDTO.setOrderId(orderId);
                productOrderPreferentialLogDTO.setAddTime(now);
                productOrderPreferentialLogDTO.setPreferentialCategory(calculatePriceDetailDTO.getPreferentialCategory());
                productOrderPreferentialLogDTO.setIdx(PreferentialCategoryEnum.getIdxByCode(calculatePriceDetailDTO.getPreferentialCategory()));
                productOrderPreferentialLogDTO.setPrice(calculatePriceDetailDTO.getPrice());
                productOrderPreferentialLogDTO.setDiscountAmount(calculatePriceDetailDTO.getDiscountAmount());
                productOrderPreferentialLogDTO.setCustomerDiscount(calculatePriceDetailDTO.getCustomerDiscount());
                productOrderPreferentialLogService.save(productOrderPreferentialLogDTO);
                productOrderPreferentialLogDTO.freeData();
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveOrder(ShopAddress shopAddress, Product product, String orderId, CalculatePriceDTO stationCalculatePrice, LocalDateTime now) {
        //订单金额
        BigDecimal orderAmount = stationCalculatePrice.getPrice();
        //实际支付金额
        BigDecimal payAmount = stationCalculatePrice.getCalculatePrice();
        //优惠金额
        BigDecimal discountAmount = (new Money(orderAmount).subtract(new Money(payAmount))).getAmount();

        ProductOrderDTO productOrderDTO = new ProductOrderDTO();
        productOrderDTO.setOrderId(orderId);
        productOrderDTO.setProductCode(product.getProductCode());
        productOrderDTO.setOrderAmount(product.getPrice());
        productOrderDTO.setName(shopAddress.getName());
        productOrderDTO.setBid(orderId);
        productOrderDTO.setPhone(shopAddress.getPhone());
        productOrderDTO.setFactory(product.getFactory());
        productOrderDTO.setAddTime(now);
        productOrderDTO.setOrderAmount(orderAmount);
        productOrderDTO.setPayAmount(payAmount);
        productOrderDTO.setDiscountAmount(discountAmount);
        productOrderDTO.setAddressId(shopAddress.getId());
        productOrderDTO.setUserId(shopAddress.getUserId());
        productOrderDTO.setPayStatus(DisplayUtil.PAY_STATUS_0);
        save(productOrderDTO);
        productOrderDTO.freeData();
    }

    /**
     * 创建订单
     *
     * @param bid       业务ID
     * @param payAmount 金额
     * @return 支付参数
     */
    public PayParamDTO createOrderPay(String bid, BigDecimal payAmount) {
        log.info("用户支付金额: {}", payAmount);
        if (SpringContextHolder.getActiveProfile().equals(SpringContextHolder.PROFILE_DEV)) {
            //测试环境，不调起支付
            return new PayParamDTO();
        }
        String subPartner = "1201712162";
        OrderDTO orderDto = new OrderDTO();
        orderDto.setThirdPartyOrder(bid).
                setPayAmount(payAmount)
                .setNotifyUrl(pandaParkConfig.getDomain() + "/gy-panda-park/app/userStroke/notify/payAfter")
                .setPersonId(SecurityUtils.getLoginUser().getUserId())
                .setType(PayConstant.ORDER_TYPE_2)
                .setAppId(SecurityUtils.getLoginUser().getUser().getAppUserExtend().getAppId())
                .setOpenid(SecurityUtils.getLoginUser().getUser().getAppUserExtend().getOpenid())
                .setPartner(pandaParkConfig.getDefaultPartner())
                .setSubPartner(subPartner);

        ObjectNode attachObjectNode = objectMapper.createObjectNode();
        orderDto.setBody("商城购买商品").setSubject("商品付款");
        attachObjectNode.put("orderType", DisplayUtil.ORDER_TYPE_5);
        orderDto.setAttach(attachObjectNode);
        ObjectNode child = objectMapper.createObjectNode();
        ObjectNode wxLite = objectMapper.createObjectNode();
        wxLite.put(UserStrokeHolderService.AMOUNT, payAmount.doubleValue());
        wxLite.put("sub_appid", SecurityUtils.getLoginUser().getUser().getAppUserExtend().getAppId());
        wxLite.put("sub_openid", SecurityUtils.getLoginUser().getUser().getAppUserExtend().getOpenid());
        child.set("wx_lite", objectMapper.createArrayNode().add(wxLite));
        orderDto.setChannelDetails(child);

        Map<String, String> params = orderHolderService.createOrder(orderDto);
        if (params.isEmpty()) {
            throw new ApiException("获取支付参数异常");
        }
        PayParamDTO payParamDTO = new PayParamDTO();
        payParamDTO.setAppId(pandaParkConfig.getDefaultMiniProgramAppId()).setNonceStr(params.get("nonceStr")).setPrepayId(params.get("package")).setPaySign(params.get("paySign")).setSignType("MD5").setTimeStamp(params.get("timeStamp")).setThirdPartyOrder(bid);
        return payParamDTO;
    }

    @Transactional(readOnly = true)
    public List<ProductOrderDTO> getListByBid(String bid) {
        QueryWrapper<ProductOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("bid", bid);
        queryWrapper.orderByAsc("add_time");
        final List<ProductOrder> list = super.list(queryWrapper);
        queryWrapper.clear();
        return modelToDtoList(list);
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateShopOrderState(String orderId, int payStatus, String userId) {
        RLock lock = redissonClient.getLock(orderId);
        try {
            try {
                boolean res = lock.tryLock(30, 10, TimeUnit.SECONDS);
                if (res) {
                    ProductOrderDTO productOrderDTO = Optional.ofNullable(getByIdDto(orderId)).orElseThrow(() -> new ApiException("订单ID值错误"));
                    if (!productOrderDTO.getUserId().equals(userId)) {
                        throw new ApiException("没有权限操作");
                    }
                    if (!productOrderDTO.getPayStatus().equals(DisplayUtil.PAY_STATUS_0)) {
                        return;
                    }
                    //只处理当前订单状态是未支付状态的，那么更新支付状态为已支付、取消支付、支付失败
                    productOrderDTO.setPayStatus(payStatus);
                    update(productOrderDTO);
                    productOrderDTO.freeData();
                } else {
                    throw new ApiException(ApiResult.error());
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error(REDIS_LOCKED_ERR, e);
                throw new ApiException(ApiResult.error());
            }
        } finally {
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public PayParamDTO payShopOrder(String orderId, String userId) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ORDER_ID, orderId);
        PayParamDTO payParamDTO = payParamRedisTemplateUtil.get(key, PayParamDTO.class);
        if (null == payParamDTO) {
            throw new ApiException("当前订单已失效，请重新下单");
        }
        RLock lock = redissonClient.getLock(orderId);
        try {
            try {
                boolean res = lock.tryLock(30, 10, TimeUnit.SECONDS);
                if (res) {
                    ProductOrderDTO productOrderDTO = Optional.ofNullable(getByIdDto(orderId)).orElseThrow(() -> new ApiException("订单ID值错误"));
                    if (!productOrderDTO.getUserId().equals(userId)) {
                        throw new ApiException("没有权限操作");
                    }
                    if (!productOrderDTO.getPayStatus().equals(DisplayUtil.PAY_STATUS_0) && !productOrderDTO.getPayStatus().equals(DisplayUtil.PAY_STATUS_4)) {
                        log.error("支付状态: {}", productOrderDTO.getPayStatus());
                        throw new ApiException("不需要操作");
                    }
                    productOrderDTO.setAddTime(LocalDateTime.now());
                    update(productOrderDTO);
                    productOrderDTO.freeData();
                    return payParamDTO;
                } else {
                    throw new ApiException(ApiResult.error());
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error(REDIS_LOCKED_ERR, e);
                throw new ApiException(ApiResult.error());
            }
        } finally {
            lock.unlock();
        }
    }
}